package com.alibaba.citrus.ots.common.transaction;

import com.alibaba.citrus.ots.common.annotation.EpochTransactional;
import com.alibaba.lattice2.epoch.db.dto.GetTransactionManagerRequest;
import com.alibaba.lattice2.epoch.db.service.UniDataAccessor;
import com.alibaba.lattice2.epoch.db.transaction.DefaultTransactionDefinition;
import com.alibaba.lattice2.epoch.db.transaction.PlatformTransactionManager;
import com.alibaba.lattice2.epoch.db.transaction.TransactionDefinition;
import com.alibaba.lattice2.epoch.db.transaction.TransactionStatus;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.UndeclaredThrowableException;

/**
 * @author zhangfei
 * @date 2021/12/20
 */
@Aspect
@Component
@Order(Integer.MAX_VALUE)
public class EpochTransactionInterceptor {

    @Autowired
    private UniDataAccessor uniDataAccessor;

    @Pointcut("@annotation(com.alibaba.citrus.ots.common.annotation.EpochTransactional)")
    private void methodPoint() {
    }

    @Around("methodPoint()")
    public Object invokeWithInTransaction(ProceedingJoinPoint proceedingJoinPoint) {
        EpochTransactional transactional = getTargetMethodAnnotation(proceedingJoinPoint, EpochTransactional.class);
        PlatformTransactionManager txManager = getTxManager(transactional);
        TransactionStatus transactionStatus = txManager.getTransaction(getDefinition(transactional));
        Object result = null;
        try {
            result = proceedingJoinPoint.proceed();
            txManager.commit(transactionStatus);
            return result;
        } catch (RuntimeException | Error ex) {
            txManager.rollback(transactionStatus);
            throw ex;
        } catch (Throwable ex) {
            txManager.rollback(transactionStatus);
            throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
        }
    }

    private <T extends Annotation> T getTargetMethodAnnotation(ProceedingJoinPoint point,
        Class<T> annotationClass) {
        return ((MethodSignature)point.getSignature()).getMethod().getAnnotation(annotationClass);
    }

    private PlatformTransactionManager getTxManager(EpochTransactional transactional) {
        String appCode = transactional.appCode();
        String dataSourceCode = transactional.dataSourceCode();
        String dataSourceType = transactional.dataSourceType();
        String dbType = transactional.dbType();
        String uniqueKey = appCode + "_" + dataSourceCode + "_" + dataSourceType + "_" + dbType;
        GetTransactionManagerRequest request = new GetTransactionManagerRequest();
        request.setAppCode(transactional.appCode());
        request.setDataSourceCode(dataSourceCode);
        request.setDataSourceType(dataSourceType);
        request.setDbType(dbType);
        return uniDataAccessor.getTransactionManager(request);
    }

    private TransactionDefinition getDefinition(EpochTransactional transactional) {
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        transactionDefinition.setPropagationBehavior(transactional.propagation().value());
        transactionDefinition.setIsolationLevel(transactional.isolation().value());
        transactionDefinition.setTimeout(transactional.timeout());
        transactionDefinition.setReadOnly(transactional.readOnly());
        return transactionDefinition;
    }
}
